package lol.clann.minecraft.springboot.plugin.bukkit.impl;

import com.ruoshui.utils.java.exception.ExceptionUtils;
import com.ruoshui.utils.java.reflect.ReflectUtils;
import com.ruoshui.utils.java.reflect.model.MuttableParameterizedType;
import com.ruoshui.utils.json.JsonUtils;
import lol.clann.minecraft.springboot.SpringBootPlugin;
import lol.clann.minecraft.springboot.api.annotation.compatibility.CompatibilityChecked;
import lol.clann.minecraft.springboot.api.annotation.condition.ConditionalOnMinecraftVersion;
import lol.clann.minecraft.springboot.api.annotation.condition.MinecraftVersionRange;
import lol.clann.minecraft.springboot.api.bukkit.constant.NBTTypeEnum;
import lol.clann.minecraft.springboot.api.bukkit.utils.*;
import lol.clann.minecraft.springboot.api.context.PluginContext;
import lol.clann.minecraft.springboot.api.model.reflect.FieldInvoker;
import lol.clann.minecraft.springboot.bukkit.utils.BukkitUtils;
import lol.clann.minecraft.springboot.bukkit.utils.CraftClasses;
import lol.clann.minecraft.springboot.bukkit.utils.NMSClasses;
import lombok.Getter;
import net.minecraft.server.v1_13_R2.IChatBaseComponent;
import net.minecraft.server.v1_14_R1.EnumChatFormat;
import net.minecraft.server.v1_14_R1.SharedConstants;
import net.minecraft.server.v1_7_R4.NBTTagCompound;
import net.minecraft.server.v1_7_R4.NBTTagList;
import net.minecraft.server.v1_7_R4.NBTTagString;
import org.bukkit.Material;
import org.bukkit.command.CommandException;
import org.bukkit.craftbukkit.v1_14_R1.util.CraftChatMessage;
import org.bukkit.craftbukkit.v1_7_R4.inventory.CraftItemStack;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author pyz
 * @date 2019/7/12 12:46 PM
 */
@Service
@CompatibilityChecked
public class ItemStackUtilsImpl_all implements ItemStackUtils {
    @Autowired
    protected NBTUtils nbtUtils;
    @Autowired
    protected PluginUtils pluginUtils;
    @Autowired
    protected MaterialUtils materialUtils;
    @Autowired
    protected ChatSerializerUtils chatSerializerUtils;
    @Getter
    protected File itemStackDir;
    @Getter
    protected File itemStacksDir;
    @Getter
    protected FieldInvoker handle;
    protected int version = BukkitUtils.getBukkitVersionNumber() / 100;

    @PostConstruct
    protected void init() throws NoSuchFieldException, NoSuchMethodException, IOException {
        itemStackDir = PluginContext.getPlugin(SpringBootPlugin.id).getDataFolder().resolve("data").resolve("itemstack").toFile();
        if (!itemStackDir.exists()) {
            itemStackDir.mkdirs();
        }
        itemStacksDir = PluginContext.getPlugin(SpringBootPlugin.id).getDataFolder().resolve("data").resolve("itemstacks").toFile();
        if (!itemStacksDir.exists()) {
            itemStacksDir.mkdirs();
        }
        handle = new FieldInvoker(ReflectUtils.getField(CraftClasses.getCraftItemStack(), "handle"));
    }

    @Override
    public <T> T toNMS(ItemStack itemStack) {
        if (itemStack == null) {
            return null;
        }
        if (itemStack instanceof CraftItemStack) {
            return (T) handle.get(itemStack);
        }
        return null;
    }

    /**
     * 把new ItemStack获得的对象,转换成CraftItemStack对象
     * @param itemStack
     * @return
     */
    @Override
    public ItemStack asCraftCopy(ItemStack itemStack) {
        if (itemStack == null) {
            return null;
        }
        if (itemStack instanceof CraftItemStack) {
            return itemStack.clone();
        }
        return CraftItemStack.asCraftCopy(itemStack);
    }

    @Override
    public Object asNMSCopy(ItemStack itemStack) {
        return CraftItemStack.asNMSCopy(itemStack);
    }

    @Override
    public ItemStack toBukkit(Object nmsItemStack) {
        return CraftItemStack.asCraftMirror((net.minecraft.server.v1_7_R4.ItemStack) nmsItemStack);
    }

    @Override
    public ItemStack copy(ItemStack itemStack) {
        return itemStack == null ? null : itemStack.clone();
    }

    @Override
    public Object toNBTTagCompound(ItemStack itemStack) {
        if (itemStack == null) {
            return null;
        }
        NBTTagCompound tag = nbtUtils.newNBTTagCompound();
        ((net.minecraft.server.v1_7_R4.ItemStack) toNMS(itemStack)).save(tag);
        if (version <= 107) {
            nbtUtils.setValue(tag, "id", itemStack.getType().name());
        }
        return tag;
    }

    @Override
    public Object getTag(ItemStack itemStack) {
        return ((net.minecraft.server.v1_7_R4.ItemStack) toNMS(itemStack)).getTag();
    }

    @Override
    public Object getOrCreateTag(ItemStack itemStack) {
        Object tag = getTag(itemStack);
        if (tag == null) {
            tag = nbtUtils.newNBTTagCompound();
            setTag(itemStack, tag);
        }
        return tag;
    }

    @Override
    public <T> T getDisplayTag(ItemStack itemStack) {
        Object tag = getTag(itemStack);
        if (tag == null) {
            return null;
        }
        return (T) nbtUtils.get(tag, "display");
    }

    @Override
    public <T> T getOrCreateDisplayTag(ItemStack itemStack) {
        Object tag = getOrCreateTag(itemStack);
        return (T) nbtUtils.getOrCreateNBTTagCompound(tag, "display");
    }

    @Override
    public void setTag(ItemStack itemStack, Object nbtTagCompound) {
        ((net.minecraft.server.v1_7_R4.ItemStack) toNMS(itemStack)).setTag((NBTTagCompound) nbtTagCompound);
    }

    @Override
    public ItemStack fromNBTTagCompound(Object nbtTagCompound) {
        if (nbtTagCompound == null) {
            return null;
        }
        if (version < 108) {
            String id = (String) nbtUtils.getValue(nbtTagCompound, "id");
            Material m = Material.getMaterial(id);
            if (m == null) {
                m = Material.STONE;
            }
            nbtUtils.setValue(nbtTagCompound, "id", (short) materialUtils.getId(m));
        }
        Object nms;
        if (version < 111) {
            nms = net.minecraft.server.v1_7_R4.ItemStack.createStack((NBTTagCompound) nbtTagCompound);
        } else if (version < 113) {
            nms = new net.minecraft.server.v1_11_R1.ItemStack((net.minecraft.server.v1_11_R1.NBTTagCompound) nbtTagCompound);
        } else {
            nms = net.minecraft.server.v1_13_R2.ItemStack.a((net.minecraft.server.v1_13_R2.NBTTagCompound) nbtTagCompound);
        }
        return toBukkit(nms);
    }

    @Override
    public String toJsonStringForRawMessage(ItemStack itemStack) {
        if (isEmpty(itemStack)) {
            return "{}";
        }
        return nbtUtils.toJsonStringForRawMessage(toNBTTagCompound(itemStack));
    }

    @Override
    public void setUnbreakable(ItemStack itemStack, boolean unbreakable) {
        if (isEmpty(itemStack)) {
            return;
        }
        setTagValue(itemStack, "Unbreakable", unbreakable);
    }

    @Override
    public boolean isUnbreakable(ItemStack itemStack) {
        if (isEmpty(itemStack)) {
            return false;
        }
        return getTagValue(itemStack, "Unbreakable", false);
    }

    @Override
    public int getRepairCost(ItemStack itemStack) {
        if (isEmpty(itemStack)) {
            return 0;
        }
        return getTagValue(itemStack, "RepairCost", 0);
    }

    @Override
    public void setRepairCost(ItemStack itemStack, int repairCose) {
        if (isEmpty(itemStack)) {
            return;
        }
        setTagValue(itemStack, "Unbreakable", repairCose);
    }

    @Override
    public List<String> getLore(ItemStack itemStack) {
        if (isEmpty(itemStack)) {
            return null;
        }
        Object displayTag = getDisplayTag(itemStack);
        if (displayTag == null) {
            return null;
        }
        NBTTagList Lore = nbtUtils.get(displayTag, "Lore");
        if (Lore == null) {
            return null;
        }
        List list = nbtUtils.getValue(Lore);
        List<String> lores = new ArrayList<>(list.size());
        if (BukkitUtils.getBukkitVersionNumber() < 11400) {
            for (Object o : list) {
                lores.add((String) NBTTypeEnum.NBTTagString.getField().get(o));
            }
        } else {
            List<net.minecraft.server.v1_14_R1.NBTTagString> list0 = list;
            for (net.minecraft.server.v1_14_R1.NBTTagString nbtTagString : list0) {
                net.minecraft.server.v1_14_R1.IChatBaseComponent line = net.minecraft.server.v1_14_R1.IChatBaseComponent.ChatSerializer.a(nbtTagString.asString());
                if (line != null) {
                    lores.add(line.getText());
                }
            }
        }
        return lores;
    }

    @Override
    public void setLore(ItemStack itemStack, List<String> lores) {
        if (isEmpty(itemStack)) {
            return;
        }
        if (lores == null || lores.isEmpty()) {
            Object displayTag = getDisplayTag(itemStack);
            if (displayTag != null) {
                nbtUtils.remove(displayTag, "Lore");
            }
            return;
        }
        NBTTagList lore = nbtUtils.newNBTTagList();
        nbtUtils.setNBTTagListType(lore, (byte) 8);
        List list = nbtUtils.getValue(lore);
        if (BukkitUtils.getBukkitVersionNumber() < 11400) {
            for (String s : lores) {
                list.add(nbtUtils.createNBTTagString(s));
            }
        } else {
            int version;
            if (BukkitUtils.getBukkitVersionNumber() < 11500) {
                version = SharedConstants.a().getWorldVersion();
            } else {
                version = net.minecraft.server.v1_15_R1.SharedConstants.getGameVersion().getWorldVersion();
            }
            for (String s : lores) {
                net.minecraft.server.v1_14_R1.IChatBaseComponent wrap = CraftChatMessage.wrapOrEmpty(s);
                list.add(nbtUtils.createNBTTagString(version >= 1803 ? CraftChatMessage.toJSON(wrap) : CraftChatMessage.fromComponent(wrap, EnumChatFormat.DARK_PURPLE)));
            }
        }
        Object displayTag = getOrCreateDisplayTag(itemStack);
        nbtUtils.set(displayTag, "Lore", lore);
    }

    @Override
    public String getDisplayName(ItemStack itemStack) {
        if (isEmpty(itemStack)) {
            return null;
        }
        if (version < 113) {
            net.minecraft.server.v1_7_R4.ItemStack nms = toNMS(itemStack);
            if (nms != null) {
                return nms.getName();
            }
        } else {
            net.minecraft.server.v1_13_R2.ItemStack nms = toNMS(itemStack);
            if (nms != null) {
                IChatBaseComponent name = ((net.minecraft.server.v1_13_R2.ItemStack) toNMS(itemStack)).getName();
                if (name != null) {
                    return name.getString();
                }
            }
        }
        return itemStack.getItemMeta().getDisplayName();
    }

    @Override
    public void setDisplayName(ItemStack itemStack, String name) {
        if (isEmpty(itemStack)) {
            return;
        }
        ItemMeta meta = itemStack.getItemMeta();
        meta.setDisplayName(name);
        itemStack.setItemMeta(meta);
    }

    @Override
    public void save(ItemStack itemStack, String key) {
        if (isEmpty(itemStack)) {
            return;
        }
        File file = new File(itemStackDir, key);
        if (file.exists()) {
            throw new CommandException("key重复");
        }
        try {
            Object nbt = toNBTTagCompound(itemStack);
            file.createNewFile();
            JsonUtils.writeValue(file, nbt);
        } catch (IOException e) {
            ExceptionUtils.throwException(e);
        }
    }

    @Override
    public ItemStack get(String key) {
        File file = new File(itemStackDir, key);
        if (!file.exists()) {
            throw new CommandException("key不存在");
        }
        Object nbt = JsonUtils.readValue(file, NMSClasses.getNBTTagCompound());
        return fromNBTTagCompound(nbt);
    }

    @Override
    public void saves(List<ItemStack> itemStacks, String key) {
        if (itemStacks == null) {
            return;
        }
        File file = new File(itemStacksDir, key);
        if (file.exists()) {
            throw new CommandException("key重复");
        }
        try {
            List<Object> nbts = new ArrayList<>(itemStacks.size());
            for (ItemStack itemStack : itemStacks) {
                if (!isEmpty(itemStack)) {
                    nbts.add(toNBTTagCompound(itemStack));
                }
            }
            file.createNewFile();
            JsonUtils.writeValue(file, nbts);
        } catch (IOException e) {
            ExceptionUtils.throwException(e);
        }
    }

    @Override
    public List<ItemStack> gets(String key) {
        File file = new File(itemStacksDir, key);
        if (!file.exists()) {
            throw new CommandException("key不存在");
        }
        MuttableParameterizedType type = new MuttableParameterizedType();
        type.setRawType(List.class);
        type.setActualTypeArguments(new Type[]{NMSClasses.getNBTTagCompound()});
        List<Object> nbts = JsonUtils.readValue(file, type);
        List<ItemStack> itemStacks = new ArrayList<>(nbts.size());
        for (Object nbt : nbts) {
            itemStacks.add(fromNBTTagCompound(nbt));
        }
        return itemStacks;
    }

    protected <T> T getTagValue(ItemStack itemStack, String key, T defaultValue) {
        Object tag = getTag(itemStack);
        if (tag == null) {
            return defaultValue;
        }
        Object v = nbtUtils.getValue(tag, key);
        return v != null ? (T) v : defaultValue;
    }

    protected void setTagValue(ItemStack itemStack, String key, Object value) {
        Object tag = getTag(itemStack);
        if (tag == null) {
            tag = nbtUtils.newNBTTagCompound();
            setTag(itemStack, tag);
        }
        nbtUtils.setValue(tag, key, value);
    }
}

